use im_rc::HashMap;

use crate::{
    parser::Value,
    runtime::{Env, FuncBinds},
};

use super::Builtin;

pub fn bind(env: &mut FuncBinds) {
    env.bind_builtin("hmap", Hash);
    env.bind_builtin("hmapInsert", Insert);
    env.bind_builtin("hmapGet", Get);
}

struct Get;

impl Builtin for Get {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        Ok(match argv {
            [Value::Hash(map), Value::String(name)] | [Value::Hash(map), Value::Symbol(name)] => {
                let Some(val) = map.get(name) else {
                    return Err(());
                };
                val.clone()
            }
            [Value::Hash(map), Value::String(name), or]
            | [Value::Hash(map), Value::Symbol(name), or] => match map.get(name) {
                Some(val) => val.clone(),
                _ => or.clone(),
            },
            _ => return Err(()),
        })
    }
}

struct Insert;

impl Builtin for Insert {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        let [Value::Hash(map), rest @ ..] = argv else {
            return Err(());
        };

        let mut map = map.clone();

        for each in rest.chunks(2) {
            match each {
                [Value::String(name), v] => {
                    map.insert(name.to_owned(), v.clone());
                }
                [Value::Symbol(name), v] => {
                    map.insert(name.to_owned(), v.clone());
                }
                _ => {
                    eprintln!("hash: invalid argument pair: {each:?}");
                    return Err(());
                }
            }
        }

        Ok(Value::Hash(map))
    }
}

struct Hash;

impl Builtin for Hash {
    fn call(&self, _: &Env, argv: &[Value]) -> Result<Value, ()> {
        let mut map = HashMap::new();

        for each in argv.chunks(2) {
            match each {
                [Value::String(name), v] => {
                    map.insert(name.to_owned(), v.clone());
                }
                [Value::Symbol(name), v] => {
                    map.insert(name.to_owned(), v.clone());
                }
                _ => {
                    eprintln!("hash: invalid argument pair: {each:?}");
                    return Err(());
                }
            }
        }

        Ok(Value::Hash(map))
    }
}
